﻿using System;

namespace FortuneUniverse.Fortune.Algorithms
{
  public struct Vector3 : IEquatable<Vector3>
  {
    public const float kEpsilon = 1E-05f;
    public const float kEpsilonNormalSqrt = 1E-15f;
    public float x;
    public float y;
    public float z;
    private static readonly Vector3 zeroVector = new Vector3(0.0f, 0.0f, 0.0f);
    private static readonly Vector3 oneVector = new Vector3(1f, 1f, 1f);
    private static readonly Vector3 upVector = new Vector3(0.0f, 1f, 0.0f);
    private static readonly Vector3 downVector = new Vector3(0.0f, -1f, 0.0f);
    private static readonly Vector3 leftVector = new Vector3(-1f, 0.0f, 0.0f);
    private static readonly Vector3 rightVector = new Vector3(1f, 0.0f, 0.0f);
    private static readonly Vector3 forwardVector = new Vector3(0.0f, 0.0f, 1f);
    private static readonly Vector3 backVector = new Vector3(0.0f, 0.0f, -1f);
    private static readonly Vector3 positiveInfinityVector = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
    private static readonly Vector3 negativeInfinityVector = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);

    public Vector3(float x, float y, float z)
    {
      this.x = x;
      this.y = y;
      this.z = z;
    }

    public Vector3(float x, float y)
    {
      this.x = x;
      this.y = y;
      this.z = 0.0f;
    }

    public static Vector3 Slerp(Vector3 a, Vector3 b, float t)
    {
      if ((double) t <= 0.0)
        return a;
      if ((double) t >= 1.0)
        return b;
      Vector3.Cross(a, b);
      return (Quaternion.Slerp(Quaternion.identity, Quaternion.FromToRotation(a, b), t) * a).normalized * (float) ((double) b.magnitude * (double) t + (double) a.magnitude * (1.0 - (double) t));
    }

    public static Vector3 Lerp(Vector3 a, Vector3 b, float t)
    {
      t = Mathf.Clamp01(t);
      return new Vector3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);
    }

    public static Vector3 LerpUnclamped(Vector3 a, Vector3 b, float t) => new Vector3(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t);

    public static Vector3 MoveTowards(
      Vector3 current,
      Vector3 target,
      float maxDistanceDelta)
    {
      Vector3 vector3 = target - current;
      float magnitude = vector3.magnitude;
      return (double) magnitude <= (double) maxDistanceDelta || (double) magnitude < 1.40129846432482E-45 ? target : current + vector3 / magnitude * maxDistanceDelta;
    }

    public float this[int index]
    {
      get
      {
        switch (index)
        {
          case 0:
            return this.x;
          case 1:
            return this.y;
          case 2:
            return this.z;
          default:
            throw new IndexOutOfRangeException("Invalid Vector3 index!");
        }
      }
      set
      {
        switch (index)
        {
          case 0:
            this.x = value;
            break;
          case 1:
            this.y = value;
            break;
          case 2:
            this.z = value;
            break;
          default:
            throw new IndexOutOfRangeException("Invalid Vector3 index!");
        }
      }
    }

    public void Set(float newX, float newY, float newZ)
    {
      this.x = newX;
      this.y = newY;
      this.z = newZ;
    }

    public static Vector3 Scale(Vector3 a, Vector3 b) => new Vector3(a.x * b.x, a.y * b.y, a.z * b.z);

    public void Scale(Vector3 scale)
    {
      this.x *= scale.x;
      this.y *= scale.y;
      this.z *= scale.z;
    }

    public static Vector3 Cross(Vector3 lhs, Vector3 rhs) => new Vector3((float) ((double) lhs.y * (double) rhs.z - (double) lhs.z * (double) rhs.y), (float) ((double) lhs.z * (double) rhs.x - (double) lhs.x * (double) rhs.z), (float) ((double) lhs.x * (double) rhs.y - (double) lhs.y * (double) rhs.x));

    public override int GetHashCode() => this.x.GetHashCode() ^ this.y.GetHashCode() << 2 ^ this.z.GetHashCode() >> 2;

    public override bool Equals(object other) => other is Vector3 other1 && this.Equals(other1);

    public bool Equals(Vector3 other) => this.x.Equals(other.x) && this.y.Equals(other.y) && this.z.Equals(other.z);

    public static Vector3 Reflect(Vector3 inDirection, Vector3 inNormal) => -2f * Vector3.Dot(inNormal, inDirection) * inNormal + inDirection;

    public static Vector3 Normalize(Vector3 value)
    {
      float num = Vector3.Magnitude(value);
      return (double) num <= 9.99999974737875E-06 ? Vector3.zero : value / num;
    }

    public void Normalize()
    {
      float num = Vector3.Magnitude(this);
      if ((double) num > 9.99999974737875E-06)
        this = this / num;
      else
        this = Vector3.zero;
    }

    public Vector3 normalized => Vector3.Normalize(this);

    public static float Dot(Vector3 lhs, Vector3 rhs) => (float) ((double) lhs.x * (double) rhs.x + (double) lhs.y * (double) rhs.y + (double) lhs.z * (double) rhs.z);

    public static Vector3 Project(Vector3 vector, Vector3 onNormal)
    {
      float num = Vector3.Dot(onNormal, onNormal);
      return (double) num >= 1E-06 ? onNormal * Vector3.Dot(vector, onNormal) / num : Vector3.zero;
    }

    public static Vector3 ProjectOnPlane(Vector3 vector, Vector3 planeNormal) => vector - Vector3.Project(vector, planeNormal);

    public static float Angle(Vector3 from, Vector3 to)
    {
      float num = Mathf.Sqrt(from.sqrMagnitude * to.sqrMagnitude);
      return (double) num >= 1.00000000362749E-15 ? Mathf.Acos(Mathf.Clamp(Vector3.Dot(from, to) / num, -1f, 1f)) * 57.29578f : 0.0f;
    }

    public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis) => Vector3.Angle(from, to) * Mathf.Sign(Vector3.Dot(axis, Vector3.Cross(from, to)));

    public static float Distance(Vector3 a, Vector3 b)
    {
      Vector3 vector3 = new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
      return Mathf.Sqrt((float) ((double) vector3.x * (double) vector3.x + (double) vector3.y * (double) vector3.y + (double) vector3.z * (double) vector3.z));
    }

    public static Vector3 ClampMagnitude(Vector3 vector, float maxLength) => (double) vector.sqrMagnitude <= (double) maxLength * (double) maxLength ? vector : vector.normalized * maxLength;

    public static float Magnitude(Vector3 vector) => Mathf.Sqrt((float) ((double) vector.x * (double) vector.x + (double) vector.y * (double) vector.y + (double) vector.z * (double) vector.z));

    public float magnitude => Mathf.Sqrt((float) ((double) this.x * (double) this.x + (double) this.y * (double) this.y + (double) this.z * (double) this.z));

    public static float SqrMagnitude(Vector3 vector) => (float) ((double) vector.x * (double) vector.x + (double) vector.y * (double) vector.y + (double) vector.z * (double) vector.z);

    public float sqrMagnitude => (float) ((double) this.x * (double) this.x + (double) this.y * (double) this.y + (double) this.z * (double) this.z);

    public static Vector3 Min(Vector3 lhs, Vector3 rhs) => new Vector3(Mathf.Min(lhs.x, rhs.x), Mathf.Min(lhs.y, rhs.y), Mathf.Min(lhs.z, rhs.z));

    public static Vector3 Max(Vector3 lhs, Vector3 rhs) => new Vector3(Mathf.Max(lhs.x, rhs.x), Mathf.Max(lhs.y, rhs.y), Mathf.Max(lhs.z, rhs.z));

    public static Vector3 zero => Vector3.zeroVector;

    public static Vector3 one => Vector3.oneVector;

    public static Vector3 forward => Vector3.forwardVector;

    public static Vector3 back => Vector3.backVector;

    public static Vector3 up => Vector3.upVector;

    public static Vector3 down => Vector3.downVector;

    public static Vector3 left => Vector3.leftVector;

    public static Vector3 right => Vector3.rightVector;

    public static Vector3 positiveInfinity => Vector3.positiveInfinityVector;

    public static Vector3 negativeInfinity => Vector3.negativeInfinityVector;

    public static Vector3 operator +(Vector3 a, Vector3 b) => new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);

    public static Vector3 operator -(Vector3 a, Vector3 b) => new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);

    public static Vector3 operator -(Vector3 a) => new Vector3(-a.x, -a.y, -a.z);

    public static Vector3 operator *(Vector3 a, float d) => new Vector3(a.x * d, a.y * d, a.z * d);

    public static Vector3 operator *(float d, Vector3 a) => new Vector3(a.x * d, a.y * d, a.z * d);

    public static Vector3 operator /(Vector3 a, float d) => new Vector3(a.x / d, a.y / d, a.z / d);

    public static bool operator ==(Vector3 lhs, Vector3 rhs) => (double) Vector3.SqrMagnitude(lhs - rhs) < 9.99999943962493E-11;

    public static bool operator !=(Vector3 lhs, Vector3 rhs) => !(lhs == rhs);

    public override string ToString() => string.Format("({0:F1}, {1:F1}, {2:F1})", new object[3]
    {
      (object) this.x,
      (object) this.y,
      (object) this.z
    });

    public string ToString(string format) => string.Format("({0}, {1}, {2})", new object[3]
    {
      (object) this.x.ToString(format),
      (object) this.y.ToString(format),
      (object) this.z.ToString(format)
    });

    public static Vector3 fwd => new Vector3(0.0f, 0.0f, 1f);

    public static float AngleBetween(Vector3 from, Vector3 to) => Mathf.Acos(Mathf.Clamp(Vector3.Dot(from.normalized, to.normalized), -1f, 1f));

    public static Vector3 Exclude(Vector3 excludeThis, Vector3 fromThat) => Vector3.ProjectOnPlane(fromThat, excludeThis);
  }
}
